import org.junit.{Before, Test}

import scala.io.{BufferedSource, Source}

class Demo23StudentExam {
  var students: List[Stu] = _
  var scores: List[Sco] = _
  var subjects: List[Sub] = _

  // 接收一个由学生id构成的List，借助Map结构关联Stu学生数据及Sub学科数据，最终输出 [学号,姓名，班级，科目，分数]
  def filterWithIdsAndPrint(ids: List[String]): Unit = {
    // 将学生、学科数据变成Map的形式
    val stuMap: Map[String, String] = students
      .map(stu => (stu.id, stu.name + "," + stu.clazz)).toMap

    val subMap: Map[String, String] = subjects
      .map(sub => (sub.subId, sub.subName)).toMap

    scores
      // 从scores中根据传入的一批ids对数据进行过滤
      .filter(sco => ids.contains(sco.id))
      .map(sco => {
        val id: String = sco.id
        val subId: String = sco.subId
        val nameClazz: String = stuMap.getOrElse(id, "anonymous,")
        val subName: String = subMap.getOrElse(subId, "")
        s"$id,$nameClazz,$subName,${sco.score}"
      }).foreach(println)
  }

  @Before
  // 加载三份数据 进行初始化
  def readFile(): Unit = {
    // 加载学生数据
    val stuBS: BufferedSource = Source
      .fromFile("data/students.txt")

    // 加载分数数据
    val scoBS: BufferedSource = Source
      .fromFile("data/score.txt")

    // 加载学科数据
    val subBS: BufferedSource = Source
      .fromFile("data/subject.txt")

    students = stuBS
      .getLines()
      .toList
      .map(line => { // 对数据进行切分并构建Stu对象
        val splits: Array[String] = line.split(",")
        val id: String = splits(0)
        val name: String = splits(1)
        val age: Int = splits(2).toInt
        val gender: String = splits(3)
        val clazz: String = splits(4)
        Stu(id, name, age, gender, clazz)
      })

    scores = scoBS
      .getLines()
      .toList
      .map(line => { // 对数据进行切分并构建Sco对象
        val splits: Array[String] = line.split(",")
        val id: String = splits(0)
        val subId: String = splits(1)
        val score: Int = splits(2).toInt
        Sco(id, subId, score)
      })

    subjects = subBS
      .getLines()
      .toList
      .map(line => { // 对数据进行切分并构建Sub对象
        val splits: Array[String] = line.split(",")
        val subId: String = splits(0)
        val subName: String = splits(1)
        val subScore: Int = splits(2).toInt
        Sub(subId, subName, subScore)
      })

    // 关闭BS
    stuBS.close()
    scoBS.close()
    subBS.close()
  }

  @Test
  def printData(): Unit = {
    students.take(10).foreach(println)
    scores.take(10).foreach(println)
    subjects.take(10).foreach(println)
  }

  @Test
  // 1、统计班级人数 [班级,人数]
  def clazzCnt(): Unit = {
    students
      .map(_.clazz)
      .groupBy(clazz => clazz)
      .map(kv => s"${kv._1},${kv._2.size}")
      .foreach(println)
  }

  @Test
  // 2、统计学生的总分 [学号,学生姓名,学生年龄,总分]
  // 通过Map进行关联
  def stuSumScore(): Unit = {
    // 计算每个学生的总分
    val sumScoreMap: Map[String, Int] = scores
      .groupBy(sco => sco.id)
      .map(kv => {
        val id: String = kv._1
        val scos: List[Sco] = kv._2
        // 将每个学生的6门科目成绩分数score取出并求和
        val sumScore: Int = scos.map(_.score).sum
        (id, sumScore) // 以二元组形式返回 最终map逻辑处理完以后会得到一个Map
      })

    // 遍历学生数据 通过学生id在sumScoreMap中 获取学生总分 实现”关联“
    students
      .map(stu => {
        val id: String = stu.id
        val sumScore: Int = sumScoreMap.getOrElse(id, 0)
        s"$id,${stu.name},${stu.age},$sumScore"
      }).foreach(println)

  }

  @Test
  // 1、统计年级排名前十学生各科的分数 [学号,学生姓名，学生班级，科目名，分数]
  def question1(): Unit = {
    // 计算学生总分 找到排名前十的学生
    val top10Ids: List[String] = scores
      .groupBy(sco => sco.id)
      .map(kv => {
        val id: String = kv._1
        val sumScore: Int = kv._2.map(_.score).sum
        (id, sumScore) // 如果返回的是二元组 则map处理完后会得到一个Map
      })
      .toList // Map中没有排序方法 所以需要转成List
      .sortBy(kv => -kv._2)
      .take(10)
      .map(_._1)

    filterWithIdsAndPrint(top10Ids)

  }

  @Test
  // 2、统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
  def question2(): Unit = {
    // 计算年级平均分 372.704
    val avgScore: Double = scores
      .map(_.score)
      .sum / students.size.toDouble
    println(avgScore)

    // 将学生数据变成Map的形式
    val stuMap: Map[String, String] = students
      .map(stu => (stu.id, stu.name + "," + stu.clazz)).toMap

    scores
      .groupBy(sco => sco.id)
      .map {
        case (id: String, scoList: List[Sco]) =>
          (id, scoList.map(_.score).sum)
      }
      .filter(kv => kv._2 > avgScore)
      .map {
        case (id: String, sumScore: Int) =>
          val nameClazz: String = stuMap.getOrElse(id, "")
          s"$id,$nameClazz,$sumScore"
      }.foreach(println)


  }

  @Test
  // 3、统计每科都及格的学生 [学号，姓名，班级，科目，分数]
  def question3(): Unit = {
    // 以科目id和科目满分构建一个Map
    val subScoreMap: Map[String, Int] = subjects
      .map(sub => (sub.subId, sub.subScore)).toMap

    // 6门科目都及格的学生id构成的List
    val everySubPassStuIds: List[String] = scores
      // 将每个学生不及格的科目成绩过滤掉
      .filter(sco => sco.score >= subScoreMap.getOrElse(sco.subId, 0) * 0.6)
      // 统计每个学生及格的科目数量
      .groupBy(sco => sco.id)
      .map(kv => (kv._1, kv._2.size))
      .filter(kv => kv._2 == 6)
      .keys
      .toList


    filterWithIdsAndPrint(everySubPassStuIds)

  }

  @Test
  // 4、统计每个班级的前三名 [学号，姓名，班级，分数]
  def question4(): Unit = {

    // 统计每个学生的总分 以学生id作为key，学生总分作为value构建Map
    val stuScoreMap: Map[String, Int] = scores
      .groupBy(sco => sco.id)
      .map(kv => {
        val id: String = kv._1
        val sumScore: Int = kv._2.map(_.score).sum
        (id, sumScore) // 如果返回的是二元组 则map处理完后会得到一个Map
      })

    students
      .map(stu => {
        val sumScore: Int = stuScoreMap.getOrElse(stu.id, 0)
        (stu.id, stu.name, stu.clazz, sumScore)
      })
      // 按照班级分组
      .groupBy(t4 => t4._3)
      .flatMap(kv => {
        val stus: List[(String, String, String, Int)] = kv._2
        // 按照总分进行降序排列取每个班级的前三名
        stus
          .sortBy(t4 => -t4._4)
          .take(3)
          .map(t4 => s"${t4._1},${t4._2},${t4._3},${t4._4}")
      }).foreach(println)

  }

  @Test
  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
  def question5(): Unit = {
    // 归一化处理 每门科目的总分并不是完全相同 都换算成百分制进行计算

    // 以科目id和科目满分构建一个Map
    val subScoreMap: Map[String, Int] = subjects
      .map(sub => (sub.subId, sub.subScore)).toMap

    val top100ids: List[String] = scores
      // 遍历scores，将所有分数转换为百分制
      .map(sco => {
        val subScore: Int = subScoreMap.getOrElse(sco.subId, 100)
        (sco.id, sco.score / subScore.toDouble * 100)
      })
      // 按每个学生分组 计算方差
      .groupBy(t2 => t2._1)
      .map(kv => {
        val id: String = kv._1
        val scoList: List[(String, Double)] = kv._2
        // 计算平均分
        val avgScore: Double = scoList.map(_._2).sum / scoList.size
        // 计算方差
        val variance: Double = scoList
          .map {
            case (id: String, score: Double) =>
              Math.pow(score - avgScore, 2)
          }.sum / scoList.size
        (id, variance)
      }).toList
      .sortBy(t2 => -t2._2)
      .take(100)
      .map(_._1)

    filterWithIdsAndPrint(top100ids)

  }


}

case class Stu(id: String, name: String, age: Int, gender: String, clazz: String)

case class Sco(id: String, subId: String, score: Int)

case class Sub(subId: String, subName: String, subScore: Int)
